Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ

specs/20260521-153452-car-app-library-integration/spec.md bd2863243bab6eb213401d949839a2bc74dde7e2 (bd286324) Text, 35.99 KB

Feature Specification: Car App Library Integration

Feature Branch: T383838feature/20260521-153452-car-app-library-integration
Created: 2026-05-21
Status: Draft
Input: Integrate Android Car App Library 1.9.0-alpha01 as a fully-featured, first-class car app
Cross-Platform Spec: N/A β€” platform-specific only (Android Auto / AAOS exclusive; CAL has no cross-platform equivalent)

Summary

Integrate the Android Car App Library 1.9.0-alpha01 into Meshtastic-Android to deliver a fully-featured, first-class automotive experience for Android Auto and Android Automotive OS. The integration creates a distraction-optimized, safety-first mesh radio interface for vehicles β€” enabling drivers to monitor mesh network status, read and reply to messages via voice, view node locations on maps, and receive emergency alerts with immediate prominence. A new T383838feature/car module houses the Android-only CAL layer while reusing all shared business logic from existing core and feature modules.

Clarifications

Session 2026-05-21

β€’ Q: How should voice commands be implemented β€” CAL built-in voice input, full Assistant App Actions, or both? β†’ A: CAL built-in voice input only (tap reply β†’ dictate β†’ send). System-level "Hey Google" commands are handled separately by the AppFunctions feature (T383838specs/20260521-091500-app-functions/), which exposes T383838sendMessage, T383838getMeshStatus, T383838listNodes, T383838getRecentMessages, and T383838getNodePosition to Android system AI (Gemini) automatically β€” including on car displays.
β€’ Q: Should the app declare NAVIGATION category for MapWithContentTemplate, or use PlaceListMapTemplate under POI? β†’ A: DECISION DEFERRED β€” originally selected POI/PlaceListMapTemplate but reopened for further research. See US-5 deferral note for open questions on NAVIGATION vs POI implications.
β€’ Q: Should the CarAppService maintain an independent BLE connection or share the phone app's existing connection? β†’ A: Shared connection β€” single Application-scoped BleConnectionManager instance via Koin. CarAppService keeps the process alive via Android Auto host; BLE connection persists at the Service/Application level, not Activity level.
β€’ Q: What observability approach should the car module use? β†’ A: Reuse existing Crashlytics with T383838car_session custom key tagging for car-specific filtering. No new observability infrastructure; tag existing analytics paths.
β€’ Q: Should the car app unlock additional features when the vehicle is parked? β†’ A: No parked-mode differentiation. Templated messaging apps provide a uniform experience regardless of driving state. Voice reply is built into ConversationItem. The Android Auto host enforces its own driving restrictions; the app just provides templates.

Goals

1. Complete automotive mesh experience β€” Deliver all seven core screens (messaging, node dashboard, channel management, emergency alerts, map, quick actions, mesh status panel) as a single release
2. Safety-first interaction model β€” Every interaction completes in ≀ 2 taps or via voice, meeting automotive distraction guidelines
3. Leverage 1.9.0-alpha01 components β€” Showcase Spotlight Sections, Condensed Items, Chips, Minimized Control Panel, Banners, Section Headers, and Expanded Headers for a modern car UI
4. Zero disruption to existing app β€” The new T383838feature/car module integrates via dependency injection without modifying existing module APIs or behavior
5. Voice-first messaging β€” Message composition defaults to voice input, with quick-reply templates as fallback for hands-free operation

Non-Goals

β€’ Firmware updates via the car interface (too complex and risky while driving)
β€’ Full settings UI in-car (a minimal parked-only subset may be considered in future)
β€’ Desktop or iOS car support (this is Android Auto / AAOS specific)
β€’ Video playback or media/audio streaming features
β€’ Compose UI interop (CAL uses its own template-based rendering system)
β€’ Google Assistant App Actions / voice command routing (handled by separate AppFunctions feature)
β€’ NAVIGATION category declaration / live map tracking (deferred to v2; v1 uses POI with PlaceListMapTemplate)
β€’ Phone app UI changes (car UI is additive only)

User Scenarios & Testing (mandatory)

User Story 1 - Read and Reply to Mesh Messages While Driving (Priority: P1)

A driver receives mesh messages from their group while on the road. They glance at the head unit to see new messages and use voice to compose a reply, keeping hands on the wheel and eyes on the road.

Why this priority: Messaging is the primary use case for Meshtastic. Enabling safe in-car messaging addresses the #1 reason users would want car integration.

Independent Test: Can be fully tested by sending a message from a second Meshtastic device, verifying it appears on the car display, and dictating a voice reply that arrives on the sender's device.

Acceptance Scenarios:

1. Given the car app is connected to a Meshtastic radio and a new message arrives, When the driver views the messaging screen, Then the new message appears within 3 seconds with sender name, timestamp, and message content visible at a glance
2. Given the driver is viewing a conversation, When they tap the reply action, Then the system presents voice input as the default composition method
3. Given the driver has initiated voice reply, When they speak their message and confirm, Then the message is sent to the correct channel/DM within 2 seconds
4. Given the driver prefers not to use voice, When they select quick-reply, Then a list of configurable template responses (e.g., "On my way", "Copy that", "10 minutes out") is presented for one-tap selection
5. Given the mesh radio is disconnected, When the driver opens messaging, Then a banner clearly indicates offline status and cached messages remain visible as read-only


User Story 2 - Emergency Alert Reception (Priority: P1)

A driver receives an emergency alert broadcast from a mesh node (SOS, hazard warning, etc.). The alert demands immediate attention with distinct visual and audio treatment, regardless of which screen is currently active.

Why this priority: Emergency alerts are life-safety critical. Failure to surface them prominently could have real-world safety consequences.

Independent Test: Can be tested by triggering an emergency broadcast from a test device and verifying the car app interrupts current activity with a banner alert.

Acceptance Scenarios:

1. Given any screen is active, When an emergency message is received, Then a high-priority banner appears immediately (within 1 second) with emergency iconography and distinct color treatment
2. Given an emergency banner is displayed, When the driver taps it, Then full emergency details are shown including sender identity, location (if available), and timestamp
3. Given an emergency alert has been received, When the driver navigates to the messaging screen, Then the emergency message appears in a Spotlight Section at the top, visually distinguished from normal messages
4. Given emergency audio alerts are enabled, When an emergency message arrives, Then an audible notification tone plays through the car's audio system


User Story 3 - Monitor Node Network Status (Priority: P2)

A driver glances at the head unit to check how many mesh nodes are in range, their signal strength, and battery levels β€” useful for caravan/convoy scenarios or checking if they're still in range of base camp.

Why this priority: Node awareness is the second-most-common Meshtastic use case and provides critical situational awareness for mobile users.

Independent Test: Can be tested by having 3+ nodes in range and verifying the dashboard displays each with correct signal/battery metrics.

Acceptance Scenarios:

1. Given the car app is connected with multiple nodes in range, When the driver opens the node dashboard, Then all known nodes are displayed as Condensed Items showing node name, signal quality indicator, and battery level
2. Given 6+ nodes are in range, When viewing the dashboard, Then at least 6 nodes are visible simultaneously without scrolling (leveraging Condensed Items)
3. Given a node goes offline, When the dashboard refreshes, Then the offline node is visually distinguished (dimmed or marked) and sorted to the bottom
4. Given the node list is displayed, When the driver taps a node, Then a detail view shows last heard time, distance (if location known), hardware model, and direct message option


User Story 4 - Switch Between Channels (Priority: P2)

A driver participating in multiple mesh channels (e.g., "Convoy", "Emergency", "General") quickly switches between them to view messages from different groups.

Why this priority: Channel management is essential for users in organized groups and must be achievable without complex navigation.

Independent Test: Can be tested by configuring 3+ channels and verifying single-tap channel switching via chips.

Acceptance Scenarios:

1. Given the device has multiple channels configured, When the messaging screen loads, Then channel chips are displayed at the top allowing single-tap switching
2. Given channel chips are visible, When the driver taps a different channel chip, Then the message list updates to show that channel's messages within 1 second
3. Given a channel has unread messages, When viewing the chip bar, Then that channel's chip displays an unread indicator (badge or visual emphasis)


User Story 5 - View Node Locations on Map (Priority: DEFERRED)

β”‚ ⚠️ DEFERRED: Map implementation is deferred pending further research and discussion on whether to
β”‚ pursue POI category (PlaceListMapTemplate, limited but simpler) or NAVIGATION category
β”‚ (MapWithContentTemplate, full-featured but triggers stricter Play Store review and conflicts with
β”‚ active nav apps). This decision has significant architectural and distribution implications that
β”‚ warrant dedicated analysis.

A driver in a convoy scenario views the locations of all mesh nodes on a map to understand relative positions and navigate toward or away from group members.

Why deferred: The choice between POI (static pins, 6-item cap, no routing conflicts) and NAVIGATION (live tracking, full map control, but exclusive with Google Maps/Waze) fundamentally shapes the UX and distribution strategy. More research needed on:
β€’ Google Maps SDK availability for AAOS (announced I/O 2026, timeline unclear)
β€’ NAVIGATION category Play Store review requirements and timeline
β€’ Whether Meshtastic's convoy use case justifies NAVIGATION exclusivity
β€’ User expectations (passive awareness vs. active routing toward nodes)

Acceptance Scenarios (to be finalized after map strategy decision):

1. Given nodes are reporting GPS positions, When the driver opens the map screen, Then node locations are displayed with correct positions
2. Given the map is displayed, When the driver selects a node, Then a detail view shows node name, distance, last update time, and option to send a direct message
3. Given the driver's own position is available, When viewing the map, Then their position is shown distinctly from other nodes
4. Given a node's position updates, When the map is visible, Then the display updates within 5 seconds


User Story 6 - Persistent Mesh Status at a Glance (Priority: P3)

While using any car app feature, the driver can glance at a persistent mini-panel showing mesh connectivity health β€” how many nodes are online, time since last message, and connection status to the radio.

Why this priority: Persistent status awareness reduces the need to navigate between screens, minimizing distraction.

Independent Test: Can be tested by verifying the minimized control panel remains visible across all screens and updates in real-time.

Acceptance Scenarios:

1. Given the car app is active on any screen, When the driver glances at the minimized control panel, Then they see: radio connection status, node count online, and time since last received message
2. Given the radio disconnects, When the status panel updates, Then it clearly indicates "Disconnected" with warning iconography
3. Given the minimized panel is visible, When the driver taps it, Then it expands to show additional detail (mesh name, own node battery, firmware version)


User Story 7 - In-Context Voice Input for Actions (Priority: P3)

A driver uses CAL's built-in voice input to compose messages and perform actions without typing β€” tapping reply then dictating, or using TTS readback of messages. System-level voice commands ("Hey Google, send Meshtastic message to John") are handled separately by the AppFunctions feature and work automatically on car displays without car module code.

Why this priority: Voice is the safest interaction modality while driving and rounds out the hands-free experience.

Independent Test: Can be tested by tapping the reply action, dictating a message via CAL voice input, and verifying delivery. System-level "Hey Google" commands are tested via the AppFunctions spec.

Acceptance Scenarios:

1. Given the car app is on a conversation screen, When the driver taps the reply action and speaks a message, Then voice composition targets that node/channel using CAL's built-in voice input API
2. Given a message is displayed, When the driver taps a "read aloud" action, Then the message is read via TTS including sender name and content
3. Given the driver initiates a direct message from the node dashboard, When they tap a node and select "message", Then voice input is presented as the default composition method with T383838FuzzyNameResolver used for node name matching


Edge Cases

β€’ What happens when the Bluetooth connection to the Meshtastic radio drops mid-conversation? β†’ Banner notification + graceful degradation to cached data, auto-reconnect in background
β€’ What happens when the message list exceeds CAL template item limits? β†’ Cap at 10 conversations with 5 messages each per Android Auto best practices; most recent first
β€’ How does the system handle very long messages that exceed car display constraints? β†’ Truncation with "..." and full message available on tap or read-aloud
β€’ What happens when outgoing messages exceed 237 bytes (Meshtastic protocol limit)? β†’ Reject with user feedback ("Message too long"); do not attempt to send
β€’ What happens when the car's system restricts interaction (e.g., car moving at speed)? β†’ No parked-mode differentiation; the templated messaging UI is uniform regardless of driving state. Voice reply is built into ConversationItem automatically. The Android Auto host enforces its own driving restrictions β€” the app provides templates only.
β€’ What happens when multiple emergency alerts arrive simultaneously? β†’ Stack as multiple banners; Spotlight Section shows all active emergencies chronologically
β€’ How does the app handle no configured channels? β†’ Show onboarding prompt directing user to configure channels on their phone first
β€’ What happens with emoji-only or admin messages? β†’ Filtered from car display entirely (not shown in conversation list or read aloud)
β€’ What happens on initial session connect with existing unread messages? β†’ Batch-load up to 50 unread messages across conversations; also post MessagingStyle notifications for read-back support
β€’ How are favorites vs recent contacts distinguished? β†’ Favorites (node.favorite == true) grouped at top of DM list with Section Header; remaining contacts sorted by last-heard, capped at 24

Requirements (mandatory)

Functional Requirements

β€’ FR-001: System MUST register as a Car App Service discoverable by Android Auto and AAOS hosts
β€’ FR-002: System MUST display incoming mesh messages in a scrollable list grouped by channel using Section Headers
β€’ FR-003: System MUST support voice-based message composition as the primary reply method
β€’ FR-004: System MUST provide quick-reply templates selectable with a single tap
β€’ FR-005: System MUST display emergency messages as high-priority Banners that overlay any active screen within 1 second of receipt
β€’ FR-006: System MUST present emergency messages in a Spotlight Section when viewing the messaging screen
β€’ FR-007: System MUST display all known mesh nodes as Condensed Items showing name, signal quality, and battery level
β€’ FR-008: System MUST support channel switching via Chips displayed at the top of the messaging screen
β€’ FR-009: ~~DEFERRED~~ β€” Map implementation deferred pending NAVIGATION vs POI category decision. See User Story 5.
β€’ FR-010: System MUST maintain a persistent Minimized Control Panel showing radio status, online node count, and last message time
β€’ FR-011: System MUST display a Banner when the Bluetooth connection to the radio is lost
β€’ FR-012: System MUST support expanding node details on tap (last heard, distance, hardware model)
β€’ FR-013: System MUST use Expanded Header Layout for the node dashboard showing mesh topology summary
β€’ FR-014: System MUST declare MESSAGING as the primary category. POI or NAVIGATION as secondary category is deferred pending map strategy decision.
β€’ FR-015: System MUST gracefully degrade to cached/read-only data when the mesh radio is disconnected
β€’ FR-016: System MUST support unread message indicators on channel Chips
β€’ FR-017: System MUST filter emoji-only and admin messages from the car display (only text messages shown)
β€’ FR-018: System MUST reject outgoing messages exceeding 237 bytes (Meshtastic packet limit) with user-visible feedback
β€’ FR-019: System MUST display at most 10 conversations and at most 5 messages per ConversationItem, per Android Auto best practices
β€’ FR-020: System MUST group direct message contacts into "Favorites" (nodes marked favorite) and "Recent" sections using Section Headers
β€’ FR-021: System MUST load up to 50 unread messages across conversations on session start, most recent first
β€’ FR-022: System MUST also implement notification-based messaging (NotificationCompat.MessagingStyle with reply and mark-as-read Actions) as required by templated messaging apps
β€’ FR-023: System MUST display transient CarToast feedback for user actions (message sent, message failed, reconnection events)
β€’ FR-024: System MUST support pull-to-refresh (OnContentRefreshListener) on message and node list screens
β€’ FR-025: System MUST present emergency alerts as modal Alert dialogs (CAL Alert API) requiring explicit acknowledgment
β€’ FR-026: System SHOULD use LongMessageTemplate for viewing full conversation history beyond the 5-message list limit
β€’ FR-027: System SHOULD provide responsive text variants (CarText.addVariant) for narrow vs wide head unit displays
β€’ FR-028: System SHOULD restrict message composition actions to parked state via ParkedOnlyOnClickListener

Non-Functional Requirements

β€’ NFR-001: All interactive elements MUST be reachable within 2 taps from any screen
β€’ NFR-002: New message display latency MUST be ≀ 3 seconds from radio receipt to screen render
β€’ NFR-003: Car app battery overhead MUST be < 10% additional drain compared to the phone app running alone
β€’ NFR-004: Car App minimum API level MUST be Car API Level 8 (required for 1.9.0 components)
β€’ NFR-005: The car module MUST NOT introduce dependencies that affect the phone app's build time by more than 5%
β€’ NFR-006: All text elements MUST meet automotive readability guidelines (minimum font sizes per OEM requirements)
β€’ NFR-007: The app MUST support both Android Auto (projection) and AAOS (embedded) deployment modes
β€’ NFR-008: Emergency alert audio MUST play through the car's notification channel, not media channel
β€’ NFR-009: Car module MUST tag all Crashlytics events with a T383838car_session custom key (value: session ID) to enable car-specific crash/ANR filtering and diagnosis
β€’ NFR-010: Screen invalidation MUST be debounced (β‰₯300ms) and MUST NOT recreate Screen objects; use T383838invalidate() to trigger T383838onGetTemplate() re-evaluation, matching CarPlay's proven refresh pattern
β€’ NFR-011: Template data refresh latency MUST be ≀500ms from invalidation trigger to rendered update

Architecture

Key Components

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Component β”‚ Module / File β”‚ Purpose β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ MeshtasticCarAppService β”‚ T383838feature/car/service/ β”‚ CAL Session host, entry point for Android … β”‚
β”‚ MessagingScreen β”‚ T383838feature/car/screens/ β”‚ Message list with channel chips, voice rep… β”‚
β”‚ NodeDashboardScreen β”‚ T383838feature/car/screens/ β”‚ Condensed Items grid of all mesh nodes β”‚
β”‚ ~~MapScreen~~ β”‚ ~~T383838feature/car/screens/~~ β”‚ ~~PlaceListMapTemplate showing node positi… β”‚
β”‚ EmergencyHandler β”‚ T383838feature/car/alerts/ β”‚ Banner management for emergency messages β”‚
β”‚ MeshStatusPanel β”‚ T383838feature/car/panels/ β”‚ Minimized Control Panel with mesh health β”‚
β”‚ CarMessageRepository β”‚ T383838core/data/ β”‚ Existing message repository (reused) β”‚
β”‚ CarNodeRepository β”‚ T383838core/data/ β”‚ Existing node repository (reused) β”‚
β”‚ ChannelManager β”‚ T383838core/domain/ β”‚ Existing channel logic (reused) β”‚
β”‚ BleConnectionManager β”‚ T383838core/ble/ β”‚ Existing BLE connection (reused; Applicati… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Component Interaction

T282828
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Android Auto / AAOS Host β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ CAL Session
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ MeshtasticCarAppService β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚ β”‚Messaging β”‚ β”‚ Nodes β”‚ β”‚ Map Screen β”‚β”‚
β”‚ β”‚ Screen β”‚ β”‚Dashboard β”‚ β”‚ β”‚β”‚
β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚ β”‚ MeshStatusPanel (persistent) β”‚β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚ β”‚ EmergencyHandler (banners) β”‚β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ Koin DI
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Shared Business Logic (core/) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚Messages β”‚ β”‚ Nodes β”‚ β”‚Channelsβ”‚ β”‚ BLE β”‚ β”‚
β”‚ β”‚ Repo β”‚ β”‚ Repo β”‚ β”‚Manager β”‚ β”‚Connectβ”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Source-Set Impact

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Source Set β”‚ Impact β”‚ Justification β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ T383838commonMain β”‚ No changes β”‚ All shared business logic already exists in core modules β”‚
β”‚ T383838androidMain β”‚ New T383838feature/car module β”‚ CAL is Android-only; entire car UI layer is platform-spe… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Design Standards Compliance

β€’ [ ] New screens reviewed against automotive HMI distraction guidelines (NHTSA Phase 2)
β€’ [ ] CAL template system used exclusively (no custom rendering that bypasses automotive safety checks)
β€’ [ ] Accessibility: Voice readback of all visual information, high-contrast automotive color schemes
β€’ [ ] Typography: Uses CAL's built-in automotive-safe text sizing (enforced by host)
β€’ [ ] Emergency alerts use distinct visual language (color, iconography) distinguishable from informational banners

Privacy Assessment

β€’ [ ] No PII, location data, or cryptographic keys logged or exposed beyond what existing modules already handle
β€’ [ ] Car app reuses existing data layer β€” no new network calls or data collection
β€’ [ ] Node location data displayed on map uses existing privacy controls (user opt-in for position sharing)
β€’ [ ] No data sent to third-party automotive services
β€’ [ ] Proto submodule (T383838core/proto) not modified (read-only upstream)

Success Criteria (mandatory)

Measurable Outcomes

β€’ SC-001: Users can read a new message and send a voice reply in under 15 seconds total interaction time
β€’ SC-002: Emergency alerts are visible to the driver within 1 second of receipt by the radio
β€’ SC-003: Node dashboard displays 6+ nodes simultaneously without scrolling (Condensed Items density)
β€’ SC-004: All primary actions (read message, reply, check nodes, view map) reachable within 2 taps from home
β€’ SC-005: Car app adds < 10% battery drain overhead compared to phone-only operation over a 1-hour driving session
β€’ SC-006: Channel switching completes (chip tap to new message list rendered) within 1 second
β€’ SC-007: App passes Android Auto App Quality review criteria for the MESSAGING category
β€’ SC-008: 95% of voice-initiated replies complete successfully without fallback to touch input
β€’ SC-009: ~~DEFERRED~~ β€” Map latency criterion deferred with map implementation
β€’ SC-010: Zero crashes or ANRs attributed to the car module during a 2-hour continuous driving session

Assumptions

β€’ Car App Library 1.9.0-alpha01 APIs are sufficiently stable for production use (alpha risk accepted per user directive)
β€’ The existing T383838core/data repositories provide all necessary data access; no new data sources required
β€’ Meshtastic radio remains paired and connected via BLE during driving (standard operating mode)
β€’ BLE connection is Application-scoped (not Activity-scoped); CarAppService keeps the host process alive so the connection naturally persists regardless of phone app Activity state
β€’ Users have already configured channels and node settings via the phone app before driving
β€’ Android Auto host enforces its own distraction-optimization rules (template item limits, interaction restrictions); the app respects these constraints
β€’ The T383838google build flavor is the distribution target; F-Droid/GitHub flavors do not include car support
β€’ Quick-reply templates are configurable via the phone app's settings; the car app consumes them read-only
β€’ Voice input quality depends on the car's microphone hardware; the app delegates to Android's speech recognition system
β€’ Map template strategy (POI vs NAVIGATION category) is deferred; no map screen in initial implementation
β€’ Minimum Car API Level 8 is required; older Android Auto hosts will not show the app (graceful absence, not crash)
β€’ Koin dependency injection is used consistently with Koin Annotations for the new module
β€’ TTS (text-to-speech) for reading messages aloud uses Android's built-in TTS engine

External References & Research

Official Documentation

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Resource β”‚ URL β”‚ Relevance β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Car App Library Release Notes β”‚ https… β”‚ 1.8.0-beta01 & 1.9.0-alpha01 component APIs β”‚
β”‚ Building Car Apps (Training) β”‚ https… β”‚ CarAppService setup, templates, lifecycle β”‚
β”‚ Templated Messaging Guide β”‚ https… β”‚ ConversationItem, voice reply, notification integration β”‚
β”‚ Notification-based Messaging β”‚ https… β”‚ MessagingStyle, reply/mark-as-read Actions β”‚
β”‚ Android Auto Add Support β”‚ https… β”‚ Manifest, automotiveappdesc.xml, projection β”‚
β”‚ Component Design Guidance β”‚ https… β”‚ Automotive HMI patterns β”‚
β”‚ Car App Quality Guidelines β”‚ https… β”‚ Review criteria for MESSAGING category β”‚
β”‚ Testing with DHU β”‚ https… β”‚ Desktop Head Unit setup and usage β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Google I/O 2026 Announcements

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Resource β”‚ URL β”‚ Key Takeaways β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Android for Cars: Unifying Platforms β”‚ ht… β”‚ CAL 1.8.0 media templates, CAL 1.9.0 components, M… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key API Patterns from Official Docs

Templated Messaging (from official guidance)

β€’ ConversationItem auto-provides voice reply + mark-as-read actions
β€’ Max 5–10 conversations, each with ≀ 5 messages
β€’ Refresh cadence: ≀ 500ms per invalidation
β€’ Must also implement notification-based messaging (MessagingStyle) as fallback
β€’ Distribution: Currently internal + closed testing tracks only (production opening later)

Manifest Requirements

T282828
T8b949e<!-- automotive_app_desc.xml for templated messaging -->
Tff7b72<automotiveAppTff7b72>
Tff7b72<uses Te6edf3name=Ta5d6ff"notification" Tff7b72/>
Tff7b72<uses Te6edf3name=Ta5d6ff"template" Tff7b72/>
Tff7b72</automotiveApp>

T8b949e<!-- CarAppService intent filter -->
Tff7b72<service Te6edf3android:name=Ta5d6ff".MeshtasticCarAppService" Te6edf3android:exported=Ta5d6ff"true"Tff7b72>
Tff7b72<intent-filterTff7b72>
Tff7b72<action Te6edf3android:name=Ta5d6ff"androidx.car.app.CarAppService" Tff7b72/>
Tff7b72<category Te6edf3android:name=Ta5d6ff"androidx.car.app.category.MESSAGING" Tff7b72/>
Tff7b72</intent-filter>
Tff7b72</service>

T8b949e<!-- Minimum Car API Level -->
Tff7b72<meta-data Te6edf3android:name=Ta5d6ff"androidx.car.app.minCarApiLevel" Te6edf3android:value=Ta5d6ff"8" Tff7b72/>


ConversationItem Pattern (from official sample)

T282828
Te6edf3ConversationItemTb4b4b4.Te6edf3BuilderTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3setConversationCallbackTb4b4b4(Te6edf3callbackTb4b4b4)
Tb4b4b4.Te6edf3setIdTb4b4b4(Te6edf3conversationTb4b4b4.Te6edf3idTb4b4b4)
Tb4b4b4.Te6edf3setTitleTb4b4b4(Te6edf3conversationTb4b4b4.Te6edf3titleTb4b4b4)
Tb4b4b4.Te6edf3setIconTb4b4b4(Te6edf3conversationTb4b4b4.Te6edf3iconTb4b4b4)
Tb4b4b4.Te6edf3setMessagesTb4b4b4(Te6edf3carMessagesTb4b4b4)
Tb4b4b4.Te6edf3setSelfTb4b4b4(Te6edf3selfPersonTb4b4b4)
Tb4b4b4.Te6edf3setGroupConversationTb4b4b4(Te6edf3conversationTb4b4b4.Te6edf3isGroupTb4b4b4)
Tb4b4b4.Te6edf3buildTb4b4b4(Tb4b4b4)


Related In-Flight Features

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Feature β”‚ Branch β”‚ Spec β”‚ Relationship β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ App Functions β”‚ T383838jamesarich/crispy-barnacle β”‚ T383838specs/20260521-091500-app-functions/ β”‚ Provides "H… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Shared Infrastructure from AppFunctions

β€’ T383838AiFunctionProvider interface in T383838core/data/commonMain β€” platform-agnostic contract for AI-driven operations
β€’ T383838FuzzyNameResolver in T383838core/data/commonMain β€” LCS-based node/channel name matching (50% threshold)
β€’ T383838RateLimiter in T383838core/data/commonMain β€” sliding window rate limiter (5 calls/60s) for mesh airtime protection
β€’ Architecture pattern: Thin Android wrappers (T383838androidApp/src/google/) calling shared business logic

Integration Points

β€’ Car module reuses T383838FuzzyNameResolver for voice reply targeting (e.g., "reply to James" β†’ resolve to node)
β€’ T383838RateLimiter can protect car-originated sends from exceeding mesh airtime
β€’ AppFunctions "Hey Google" commands work on car displays automatically (system-level, no car module code needed)
β€’ Both features share: T383838NodeRepository, T383838CommandSender, T383838RadioConfigRepository, T383838PacketRepository

CAL 1.9.0-alpha01 Component Reference

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Component β”‚ API Class β”‚ Min Car API β”‚ Use in Meshtastic β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Spotlight Section β”‚ T383838SpotlightSection.Builder() β”‚ 8 β”‚ Emergency messages pinned … β”‚
β”‚ Condensed Items β”‚ T383838CondensedItem.Builder() β”‚ 8 β”‚ Dense node list (6+ visibl… β”‚
β”‚ Chips β”‚ T383838Chip.Builder() β”‚ 8 β”‚ Channel switching + unread… β”‚
β”‚ Minimized Control Panel β”‚ T383838SectionedItemTemplate β”‚ 8 β”‚ Persistent mesh status str… β”‚
β”‚ Banners β”‚ T383838Banner.Builder() β”‚ 8 β”‚ Emergency overlay + discon… β”‚
β”‚ Section Headers β”‚ T383838SectionHeader.Builder() β”‚ 8 β”‚ Message grouping by channel β”‚
β”‚ Expanded Header Layout β”‚ T383838Header.Builder() β”‚ 8 β”‚ Mesh topology summary (nod… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Distribution Constraints (as of May 2026)

β€’ Templated messaging apps: Internal + closed testing tracks only on Play Store
β€’ Production track: Not yet open for templated messaging category
β€’ AAOS: Separate distribution channel (OEM app stores or Play for Automotive)
β€’ F-Droid: Excluded (CAL requires Google Play Services)
β€’ Timeline: Production track expected to open "later" per Google (no firm date)

Cross-Platform Parity: Meshtastic-Apple CarPlay

Source: T383838Meshtastic-Apple/Meshtastic/CarPlay/ (main branch, May 21, 2026)

Apple CarPlay features (shipped):
β€’ Two-tab UI: Channels + Direct Messages (with Favorites/Recent sections)
β€’ SiriKit voice compose/read-back via T383838INSendMessageIntent
β€’ Unread badges per channel and per DM
β€’ "Not Connected" graceful degradation
β€’ Live Activity (Dynamic Island) with node telemetry stats
β€’ Batch donation of 50 unread messages on session start
β€’ 300ms debounced refresh (updateSections, not rebuild)
β€’ Message search via T383838INSearchForMessagesIntent
β€’ Message filtering: no emoji-only, no admin messages
β€’ 200-byte message limit enforcement

Parity decisions incorporated into this spec:
β€’ FR-017: Message filtering (emoji/admin exclusion) β€” matches Apple
β€’ FR-018: Message size limit enforcement β€” matches Apple (237 bytes for Meshtastic)
β€’ FR-019: Conversation caps (10 convos, 5 msgs each) β€” per Android guidance
β€’ FR-020: Favorites section grouping β€” matches Apple's Favorites/Recent pattern
β€’ FR-021: Session start unread batch load β€” matches Apple's 50-message donation
β€’ FR-022: Notification-based messaging fallback β€” required per Android templated messaging docs
β€’ NFR-010: Refresh debouncing (β‰₯300ms) β€” matches Apple's proven 300ms debounce
β€’ NFR-011: Refresh latency (≀500ms) β€” matches Apple's observed performance

Android-exclusive features (exceeding Apple):
β€’ Node dashboard with Condensed Items (Apple has no node visibility)
β€’ Emergency Banner overlays with audio alerts (Apple shows emergencies as regular messages)
β€’ ~~Map integration~~ (DEFERRED pending NAVIGATION vs POI decision)
β€’ Channel Chips for instant switching (Apple requires tab navigation)
β€’ Quick-reply templates (Apple only offers Siri voice)
β€’ Visual hierarchy via Spotlight/Section Headers/Expanded Headers
β€’ Persistent Minimized Control Panel (Apple uses separate Live Activity)

Deferred to v2 (Apple has, we don't yet):
β€’ Message search (SearchTemplate or via AppFunctions)
β€’ Live Activity equivalent (Android ongoing notification with mesh telemetry)

Served by rngit 1.5.4 - Generated in 0.07s